using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using NUnit.Framework;

namespace CodeBackpack.Plist.Test
{
    [TestFixture]
    public class PropertyListXmlReaderTestFixture
    {
        [Test]
        public void Ctor()
        {
            new PropertyListXmlReader(TextReader.Null);
        }

        [Test, ExpectedException(typeof (ArgumentNullException))]
        public void CtorNullArg()
        {
            new PropertyListXmlReader(null);
        }

        [Test, ExpectedException(typeof (XmlException))]
        public void UnexpectedDtdPubid()
        {
            String plistXml = @"
<!DOCTYPE plist PUBLIC ""-//Gooseberry Computer//DTD PLIST 1.0//EN"" ""http://irrelevant/"">
<plist version=""1.0"" />
";
            using (TextReader textReader = ReadString(plistXml))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    new PropertyList().Load(reader);
                }
            }
        }

        [Test, ExpectedException(typeof (XmlException))]
        public void InvalidRootElement()
        {
            using (TextReader textReader = ReadString("<woot />"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    new PropertyList().Load(reader);
                }
            }
        }

        [Test, ExpectedException(typeof (XmlException))]
        public void InvalidEpilog()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'> <integer>1</integer> <false/> </plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    new PropertyList().Load(reader);
                }
            }
        }

        [Test]
        public void EmptyPlistElement()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'/>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    Assert.IsNull(new PropertyList().Load(reader).Value);
                }
            }
        }

        [Test]
        public void PlistWithoutData()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'>  </plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    Assert.IsNull(new PropertyList().Load(reader).Value);
                }
            }
        }

        [Test, ExpectedException(typeof (XmlException))]
        public void PlistWithoutVersion()
        {
            using (TextReader textReader = ReadString("<plist/>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    new PropertyList().Load(reader);
                }
            }
        }

        [Test]
        public void PlistBytes()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'><data>AQIDBAU=</data></plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    Assert.AreEqual(new byte[] {1, 2, 3, 4, 5}, new PropertyList().Load(reader).Bytes);
                }
            }
        }

        [Test]
        public void PlistString()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'><string>hello</string></plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    Assert.AreEqual("hello", new PropertyList().Load(reader).String);
                }
            }
        }

        [Test]
        public void PlistInt()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'><integer>-42</integer></plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    Assert.AreEqual(-42, new PropertyList().Load(reader).Int);
                }
            }
        }

        [Test]
        public void PlistLong()
        {
            using (
                TextReader textReader =
                    ReadString("<plist version='1.0'><integer>9223372036854775807</integer></plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    Assert.AreEqual(long.MaxValue, new PropertyList().Load(reader).Long);
                }
            }
        }

        [Test]
        public void PlistDouble()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'><real>3.1415926535897931</real></plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    Assert.AreEqual(Math.PI, new PropertyList().Load(reader).Double);
                }
            }
        }

        [Test]
        public void PlistTrue()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'><true/></plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    Assert.AreEqual(true, new PropertyList().Load(reader).Bool);
                }
            }
        }

        [Test]
        public void PlistTrueWithEmptyContent()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'><true>   </true></plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    Assert.AreEqual(true, new PropertyList().Load(reader).Bool);
                }
            }
        }

        [Test]
        public void PlistFalse()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'><false/></plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    Assert.AreEqual(false, new PropertyList().Load(reader).Bool);
                }
            }
        }

        [Test]
        public void PlistFalseWithEmptyContent()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'> <false>  </false>  </plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    Assert.AreEqual(false, new PropertyList().Load(reader).Bool);
                }
            }
        }

        [Test, ExpectedException(typeof (XmlException))]
        public void PlistTrueWithContentNotAllowed()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'><true>a</true></plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    Assert.AreEqual(true, new PropertyList().Load(reader).Bool);
                }
            }
        }

        [Test, ExpectedException(typeof (XmlException))]
        public void PlistFalseWithContentNotAllowed()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'><false>b</false></plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    Assert.AreEqual(false, new PropertyList().Load(reader).Bool);
                }
            }
        }

        [Test]
        public void PlistDateTime()
        {
            using (
                TextReader textReader = ReadString("<plist version='1.0'><date>2007-12-28T07:00:00.042Z</date></plist>")
                )
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    DateTime t = new PropertyList().Load(reader).DateTime;
                    Assert.AreEqual(DateTimeKind.Utc, t.Kind);
                    Assert.AreEqual(2007, t.Year);
                    Assert.AreEqual(12, t.Month);
                    Assert.AreEqual(28, t.Day);
                    Assert.AreEqual(7, t.Hour);
                    Assert.AreEqual(0, t.Minute);
                    Assert.AreEqual(0, t.Second);
                    Assert.AreEqual(42, t.Millisecond);
                }
            }
        }

        [Test, ExpectedException(typeof (XmlException))]
        public void PlistWrongTag()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'><fooBar>stuff</fooBar></plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    new PropertyList().Load(reader);
                }
            }
        }

        [Test, ExpectedException(typeof (XmlException))]
        public void PlistWrongXmlNode()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'> unexpected text </plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    new PropertyList().Load(reader);
                }
            }
        }

        [Test]
        public void EmptyDict()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'><dict/></plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    PropertyList plist = new PropertyList().Load(reader);
                    Assert.IsInstanceOfType(typeof (IDictionary<String, PropertyList>), plist.Value);
                    Assert.AreEqual(0, plist.Dict.Count);
                }
            }
        }

        [Test]
        public void EmptyDict2()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'> <dict> <!-- comment --> </dict> </plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    PropertyList plist = new PropertyList().Load(reader);
                    Assert.IsInstanceOfType(typeof (IDictionary<String, PropertyList>), plist.Value);
                    Assert.AreEqual(0, plist.Dict.Count);
                }
            }
        }

        [Test, ExpectedException(typeof (XmlException))]
        public void DictEntryWithoutKey()
        {
            using (
                TextReader textReader = ReadString("<plist version='1.0'> <dict> <string>ho</string> </dict> </plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    new PropertyList().Load(reader);
                }
            }
        }

        [Test]
        public void OneDictEntry()
        {
            using (
                TextReader textReader =
                    ReadString("<plist version='1.0'> <dict> <key>abc</key> <true/> </dict> </plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    PropertyList plist = new PropertyList().Load(reader);
                    Assert.IsTrue(plist["abc"].Bool);
                }
            }
        }

        [Test, ExpectedException(typeof (XmlException))]
        public void DictEntryKeyEmpty()
        {
            using (TextReader textReader = ReadString("<plist version='1.0'> <dict> <key/> <true/> </dict> </plist>"))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    new PropertyList().Load(reader);
                }
            }
        }


        [Test]
        public void CompoundPlist()
        {
            String plistXml =
                @"
<!DOCTYPE plist PUBLIC ""-//Apple Computer//DTD PLIST 1.0//EN"" ""http://www.apple.com/DTDs/PropertyList-1.0.dtd"">
<plist version=""1.0"">
  <array>
    <string> oh la la </string>
    <integer>42</integer>
    <dict>
      <key>foo</key>  <string>foo's value</string>
      <key>bar</key>  <date>2007-12-28T07:05:46.5884526Z</date>
      <key>woot</key> <array>
         <true />
         <false />
         <string>abc</string>
      </array>
    </dict>
  </array>
</plist>
";
            using (TextReader textReader = ReadString(plistXml))
            {
                using (PropertyListReader reader = new PropertyListXmlReader(textReader))
                {
                    PropertyList plist = new PropertyList().Load(reader);
                    Assert.AreEqual(3, plist.List.Count);
                    Assert.AreEqual(" oh la la ", plist[0].String);
                    Assert.AreEqual(42, plist[1].Int);
                    Assert.AreEqual(3, plist[2].Dict.Count);
                    Assert.AreEqual("foo's value", plist[2]["foo"].String);
                    Assert.AreEqual(46, plist[2]["bar"].DateTime.Second);
                    PropertyList wootPlist = plist[2]["woot"];
                    Assert.AreEqual(3, wootPlist.List.Count);
                    Assert.IsTrue(wootPlist[0].Bool);
                    Assert.IsFalse(wootPlist[1].Bool);
                    Assert.AreEqual("abc", wootPlist[2].String);
                }
            }
        }

        private static TextReader ReadString(string text)
        {
            return new StringReader(text);
        }
    }
}